<?php
// +----------------------------------------------------------------------
// | 杭州十禾信息科技有限公司
// +----------------------------------------------------------------------
// | Copyright (c) 2013-2015 http://www.shelpe.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: LeePuman <ysyzlsm@163.com>
// +----------------------------------------------------------------------

class ProductModel extends NewzanModel{
    
    protected $tableName = 'products';
    											//'num_iid,nick,title,pic_url,location,list_time,delist_time,cid, item_imgs.url,props,props_name'
    const ITEM_GET_FIELDS = 'item_img,num_iid,title,nick,type,cid,pic_url,num,price,list_time,modified,delist_time,detail_url,props,props_name,property_alias,stuff_status';
    
    /**
     * 
     * 得到一个用户的所有产品的分类
     * @param unknown $user_id
     * @return mixed
     */
    public function getUserCats($user_id){
    	$sql = 'SELECT DISTINCT(cid),cat_name FROM `products` WHERE user_id = ' . $user_id;
    	$products = M('products')->query($sql);
    	return $products;
    }
    
    
    /**
     * 
     * get the cat name by cat_id,
     * @param int, $cat_id 
     * @param bool, $path , if it's a leaf cat then get all the cat name by order, default is set to false
     * @return mixed , the cat name(s) for this cat id
     * 				ex1 : array( 
     * 						cat_id => array( 'cat_id' => 12312321, 'cat_name' => afdsfds , 'parent_id' => ),
     * 
     * 					)
     */
    public function getCatNameByCatId($cat_id , $path = false){
		$cat = M('tb_category')->where( 'cat_id = ' . $cat_id )->find();
		if(!$path){
			return $cat;
		}
		$cats = array();
		$cats[] = $cat;
		while($cat && $cat['cat_path_id'] != $cat['cat_id']){
			$cat = M('tb_category')->where( 'cid = ' . $cat['cat_path_id'] )->find();
			$cats[] = $cat;
		}
		return array_reverse($cats);
    }
    
    /**
     * 根据关键词搜索产品.
     * TODO 没有nick参数, 因此代理账号无法进行搜索.
     * 
     * @param int $user_id
     * @param string $q
     * @param string $cids
     * @param int $page
     * @param int $size
     */
    public function searchProducts($user_id, $q, $cid = '', $page = 1, $size = 100) {
    	$fields = 'product_id,name,pic_url,cid,props,price,tsc';
    	$params = array(
    			'fields' => $fields,
    			'q' => $q,
    			'page_no' => $page ? $page : 1,
    			'page_size' => page_size,
    	);
    	$result = $this->_api->SimbaAdgroupOnlineitemsvonGetRequest($params);
    	$result = object_to_array($result);
    	$total_num = $result['total_results'];
    	$products = $result['products'];
    	return $products;
    }

    public function getUserProducts($user_id , $q = "",  $cid = "" , $page = 1 , $size = 20 , $order = 'sales_count', $desc = 'desc',$filter=null, $selectAll = false){
    	$whr = array(
    				'user_id' => $user_id
    			);
	    if($selectAll) {
	    	$products = $this->field('num_iid, title, price, publish_time, quantity, sales_count, cid, cat_name, detail_url, pic_url, item_imgs, num, props, props_name, property_alias, type, stuff_status')->where($whr)->select();
	    } else {
	    	if($q){
	    		$whr['title'] = array( 'LIKE' , "%$q%");
	    	}
	    	if($cid	){
	    		$whr['cid'] = $cid;
	    	}
	    	if($filter){
	    		$whr['cid']=array('neq',$filter);
	    	}
	    	$products = $this->field('num_iid, title, price, publish_time, quantity, sales_count, cid, cat_name, detail_url, pic_url, item_imgs, num, props, props_name, property_alias, type, stuff_status')->where($whr)->limit($size)->page($page)->order("$order $desc" )->select();
	    }
    	// total
    	$total = $this->where($whr)->count();
    	if(!$products) $products = array();
    	return array('total' => $total,  'products' => $products , 'size' => $size , 'page' => $page );
    }

    public function getProductByIds($user_id , $ids = '' , $useApi = false){
    	$whr = array( 'user_id' => $user_id );
    	if( !is_array($ids) ) $ids = explode(',' , $ids);
    	//确保sql操作不会出现错误
    	if(empty($ids)) return array();
    	$whr['num_iid'] = array( 'IN' , $ids) ;
		$products = $this->where($whr)->select( array('assoc_key' => true));
		$products = $products ? $products : array();
		//如果找出来的数量不对，并且允许使用api的话，尝试使用api进行查找
		if( (count($products) != count($ids)) && $useApi ){
		    $local_ids = array_keys($products);
		    $product_ids = array_diff( $ids , $local_ids);
		    $page_size = 20; // api限制
		    $page_count = ceil(count($product_ids) / $page_size);
		    $params = array('fields' => ProductModel::ITEM_GET_FIELDS);
		    for($page = 1; $page <= $page_count; ++$page) {
		        $page_prodcut_ids = array_slice($product_ids, ($page - 1) * $page_size, $page_size);
		        $params['num_iids'] = implode(',' , $page_prodcut_ids);
		        $resp = $this->_api->ItemsSellerListGetRequest($params);
		        $resp = object_to_array($resp);
		        $items = $resp['items']['item'];
		        foreach($items as &$item) {
		            $products[ (string) $item['num_iid']] = $item;
		        }
		    }
		}
		
		return $products;
    }
    
	public function getProductById($productId , $type = 'C', $fields = null, $useApi = true, $forceApi = false)
	{
		$me = session('me');
		$product = null;
		if($fields)
			$this->field(is_array($fields) ? implode(',', $fields) : $fields);
		if(!$forceApi) {
			$product = $this->where(array('user_id' => $me['user_id'], 'num_iid' => $productId))->find();
		}		
		return $product;
	}
  
	public function getProductProps($product/* , $productId = 0 */, $type = 'C') {
		$wordLengthThres = 6; //  大于该长度的词没啥意思, 将被忽略.
		
		$productProps = null;
		if(!is_array($product)) {
			$propTypes = array(
					'C'=>'title,cid,props_name',  // desc,property_alias,input_str,
					'B'=>'name,cid,cat_name,props_str,binds_str,sale_props_str,customer_props,property_alias', // desc,seller_cids,wap_desc
			);
			$productProps = $this->getProductById($product, $type, $propTypes[$type]);
		} else {
			$productProps = $product;
		}
		
		$propWords = array();
		//@TODO weiping写的shit代码，其实有很多系统属性的。需要增加一个列表来保存无意义的属性
		$excludedProps = array('价格', '尺码', '成分含量', '主图来源', '货号', '面料主材质含量', '产品等级'); 
		$excludedProps = array_fill_keys($excludedProps, 0);
		if($type == 'C') {
			// sample 'prop_names': 20418023:157305307:主图来源:自主实拍图;20000:131655292:品牌:Z.Z.M;2915574:11292600:板型:蝙蝠型;1632501:6603540:货号:28351;1626927:148653120:厚薄:适中(12针);20664:6384766:风格:通勤;31609:103422:款式:套头;2916845:27437175:组合形式:单件套;31610:145656893:衣长:中长款(衣长大于65CM);20672:29444:袖长:长袖;20673:32499857:领子:低圆领;2917380:27924515:袖型:常规袖;20665:29700:图案:其它图案;2917619:148618719:流行元素/工艺:印花/印染;13328588:145656297:成分含量:95%以上;1626974:28351:面料:羊绒;10995144:148625542:上市年份/季节:2012秋季;21541:114128594:价格:800-1500元;1627207:3232479:颜色分类:深紫色;20509:28315:尺码:M;20509:28316:尺码:L;20509:28317:尺码:XL;20509:28318:尺码:XXL;20509:28319:尺码:XXXL
			$props = explode(';', $productProps['props_name']);
			foreach($props as $prop) {
				$propKeyValues = explode(':', $prop); // 2915574:11292600:板型:蝙蝠型
				$propWords[$propKeyValues[2]] = $propKeyValues[3];
			}
		} else {
			// sample 'props_str':
			$props = explode(';', $productProps['props_str']);
			foreach($props as $prop) {
				$propKeyValues = explode(':', $prop); // 品牌:诺基亚;型号:N73
				$propWords[$propKeyValues[0]] = $propKeyValues[1];
			}
		}
		$propWords = array_diff_key($propWords, $excludedProps);
		
		// Exclude '其他XX'， '其它XX'，和' XX-XX', and split props with '/'
		$splitProps = array();
		foreach($propWords as $prop => $propVal) { // TODO 更多规则，可使用正则，也可以尝试识别语意。
// 			if(in_array(mb_substr($propVal, 0, 2, 'utf-8'), array('其他', '其它'))
// 					|| in_array(mb_substr($propVal, -2, 2, 'utf-8'), array('以上', '以下'))
// 					|| strpos($propVal, '-') !== false) {
			if(preg_match('/(其他|其它|以上|以下|-|<|≤|>|≥)/', $propVal)) {
				unset($propWords[$prop]);
			} else if(strpos($propVal, '/') !== false) { //对带 ‘/’的属性，将其分开。
				$tmpSplitProps = explode('/', $propVal);
				for($i = 0; $i < count($tmpSplitProps); ++$i) {
					if(mb_strlen($tmpSplitProps[$i]) <= $wordLengthThres)
						$splitProps["$prop$i"] = $tmpSplitProps[$i];
				}
				unset($propWords[$prop]);
			} else if(!preg_match('/[a-zA-Z] [a-zA-Z]/', $propVal)) { // 带空格且前后不是英文字母，将其分开.
				$tmpSplitProps = explode(' ', $propVal);
				for($i = 0; $i < count($tmpSplitProps); ++$i) {
					if(mb_strlen($tmpSplitProps[$i]) <= $wordLengthThres)
						$splitProps["$prop$i"] = $tmpSplitProps[$i];
				}
				unset($propWords[$prop]);
			} else if(mb_strlen($propVal) > $wordLengthThres) { // 太长的词没有什么意思
				unset($propWords[$prop]);
			}
		}
		
		return $propWords + $splitProps;
	}
	
	/**
	 * 猜测这个产品是什么, 比如长裤, 长袖衬衣, 移动电源...
	 * 利用这个加上属性可以组合出大量词语
	 * 
	 * @param string $productName 产品标题
	 * @param string $cateName 类目名称
	 * @return string 产品类型. 如果没有找到则返回null.
	 */
	public function guessProductType($productName, $cateName) {
		$pattern = '/^其[他它她](.*)/';
		$isOther = preg_match($pattern, $cateName);
		if($isOther)
			return null;
			
		$candidates = explode('/', $cateName);
		if(count($candidates) == 1) {
			return $candidates[0];
		}
		$pattern = '';
		foreach ($candidates as $candidate) {
			if(mb_strlen($candidate, 'utf-8') > 1) {
				$pattern .= '|' . str_replace('|', '\\|', $candidate);
			}
		}
		if($pattern != '') {
			$pattern  = '/' . substr($pattern, 1) . '/';
			$matches = null;
			preg_match($pattern, $productName, $matches);
			if(!empty($matches))
				return $matches[0];
		}
		return null;
	}
	
	/**
	 * 将给定的词组合成新词.
	 * 
	 * @param array(string) $words	基础词. <strong>注意: </strong>词的个数必须大于等于$combineNum, 否则返回空数组. 
	 * 				数组必须以自然数为键.
	 * @param int $combineNum 将多少个词组成一个新词. 目前只支持2和3.
	 * @return array(wrod)	组合词. 如果基础词的个数必须小于$combineNum, 返回空数组.
	 */
	public function combineWords($words, $combineNum) {
		if($combineNum != 2 && $combineNum != 3) {
			throw_exception('只支持组合2个或者3个词.');
		}
		$n = count($words);
		if($n < 2)
			return array();
		
		$combinedWords = array();
		for($i = 0;  $i < $n; ++$i) {
			for($j = $i + 1; $j < $n; ++$j) {
				$combined = ($words[$i] . $words[$j]); //  . ' '
				$combinedWords[] = $combined;
				if($combineNum >= 3) {
					for($k = $j + 1; $k < $n; ++$k) {
						$combinedWords[] = ($combined . $words[$k]); //  . ' '
					}
				}
			}
		}
		return $combinedWords;
	}
	
	/**
	 * 将产品标题分词，并结合产品名，进行组词。
	 * @param DbRecord $adgroup 一条adgroups表记录，必须确保已经推测了产品名。
	 * @param int $baseWeight
	 * @return array(string => int) 标题分词+产品吗的组合词: array(word => weight).
	 */
	public function getTitleWords($adgroup, $baseWeight = 60) {
		import("@.Library.Carine");
		$carine = new Carine();
		$resp = $carine->segWords($adgroup['title']);
		$respobj = json_decode($resp, true);
		if($respobj['status'] != 200) {
			throw_exception('分词服务出现错误：' . $respobj['response']);
		}
		$titleSegs = $respobj['response']; //结果已经去重并都转为小写了。
		sort( $titleSegs );
		$productType = $adgroup['product_type'];
		
		$titleWords = array();
		if($adgroup['product_type'] != 'NULL') { // 以前分析过并且成功
			foreach($titleSegs as $titleWord) {
			    $titleWords["$titleWord"] = $baseWeight + 1;
				$titleWords["$titleWord$productType"] = $baseWeight + 3;
			}
			// 2个prop组合
			$combinedTitleWords = $this->combineWords($titleSegs, 2);
			foreach($combinedTitleWords as $combinedTitleWord) {
			    $titleWords["$combinedTitleWord"] = $baseWeight + 5;
				$titleWords["$combinedTitleWord$productType"] = $baseWeight + 5;
			}
			// 3个prop组合
			if(count($titleSegs) <= 8) {
				$combinedTitleWords = $this->combineWords($titleSegs, 3);
				foreach($combinedTitleWords as $combinedTitleWord) {
					$titleWords["$combinedTitleWord$productType"] = $baseWeight + 6;
					$titleWords[$combinedTitleWord] = $baseWeight + 4;
				}
			}
		} else if(!$adgroup['product_type']) { // 以前没有分析过产品类型
			$combinedTitleWords = $this->combineWords($titleSegs, 2);
			foreach($combinedTitleWords as $combinedTitleWord) {
				$titleWords[$combinedTitleWord] = $baseWeight + 2;
			}
			if(count($titleSegs) <= 10)
				$combinedTitleWords = array_merge($combinedTitleWords, $this->combineWords($titleSegs, 3));
			foreach($combinedTitleWords as $combinedTitleWord) {
				$titleWords[$combinedTitleWord] = $baseWeight + 4;
			}
		}
		return $titleWords;
	}
	
	/**
	 * 根据产品属性和产品类型组词. 
	 * 如果不能获取产品类型, 则只将属性的 C(n, 1), C(n, 2), C(n, 3) 组合组成词语.
	 * 如果能够获取产品类型, 则除了上面的属性组合外, 再将这些组合+产品类型组词.
	 * 
	 * @param array $adgroup 推广组adgroups表的一条记录. 给定$adgroup 或者 $product+$category都可以.
	 * @param int $baseWeight 基础权重. 
	 * @return array(string => int) 属性组合词数组: array( word => weight)
	 */
	public function getPropTypeWords($adgroup, $baseWeight = 80) {
		if(empty($adgroup)) {
			throw_exception('参数错误: 必须给定adgroup');
		}
		$adgroupMdl = new AdgroupModel();
		$props = array();
		$productType = '';
		if(!empty($adgroup['product_type'])) {
			$props = $adgroup['product_props'] ? explode(',', $adgroup['product_props']) : array();
			sort($props);
			$productType = $adgroup['product_type'];
		} else {
			$taobaoProduct = $this->getProductById($adgroup['num_iid'], 'C', 'num_iid,title,cid,props_name');
			$category = $taobaoProduct['cate_name'] ?  $taobaoProduct['cate_name']
					: (!empty($taobaoProduct['cid']) ? D('Category')->getCateName($taobaoProduct['cid']) : '');
				
			$props = $this->getProductProps($taobaoProduct);
			$props = zh_arrtolower(array_values($props)); // reset the keys
			$productType = zh_strtolower($this->guessProductType($taobaoProduct['title'], $category));
			
			// save the value into table 'adgroups'
			$adgroupMdl->where(array('adgroup_id' => $adgroup['adgroup_id']))->save(array(
					'product_type' => $productType ? $productType : 'NULL', // 没有分析出产品类别的, 用NULL标记一下, 以免下次重新分析
					'product_props' => join(',', $props)
			));
		} 
// 		dbg($props, '$props');
// 		dbg($productType, '$productType');

		$propWords = array();
		if($productType && $productType != 'NULL') {
			// 只有商品名
			$propWords[$productType] = $baseWeight + 1;
			if($props) {
				if(!is_array($props)) // 针对NOTICE错误：NOTIC: [2] Invalid argument supplied for foreach()。
					throw_exception('内部错误：$props不是数组。');
				// 单个prop
				foreach($props as $prop) 
					$propWords["$prop$productType"] = $baseWeight + 3;
				// 2个prop组合
				$combinedProps = $this->combineWords($props, 2);
				foreach($combinedProps as $combinedProp) {
					$propWords["$combinedProp$productType"] = $baseWeight + 5;
	// 				$propWords[] = array($combinedProp, 22);
				}
				// 3个prop组合
				if(count($props) <= 10) {
					$combinedProps = $this->combineWords($props, 3);
					foreach($combinedProps as $combinedProp) {
						$propWords["$combinedProp$productType"] = $baseWeight + 6;
						$propWords[$combinedProp] = $baseWeight + 4;
					}
				}
			}
		} else {
			$combinedProps = $this->combineWords($props, 3);
			foreach($combinedProps as $combinedProp) {
				$propWords[$combinedProp] = $baseWeight + 2;
			}
			if(count($props) <= 12)
				$combinedProps = array_merge($combinedProps, $this->combineWords($props, 3));
			foreach($combinedProps as $combinedProp) {
				$propWords[$combinedProp] = $baseWeight + 4;
			}
		}
		
		return $propWords;
	}
	
	/**
	 * 确保已经根据adgroup标题和类目推测出产品类型，并且获取有效的属性。
	 * @param DbRecord& $adgroup adgroups表记录。引用传参。
	 */
	public function confirmProductTypeProps(&$adgroup)
	{
		if(empty($adgroup['product_type'])) {
			$taobaoProduct = $this->getProductById($adgroup['num_iid'], 'C', 'num_iid,title,cid,props_name');
			$category = $taobaoProduct['cat_name'] ?  $taobaoProduct['cat_name']
					: (!empty($taobaoProduct['cid']) ? D('Category')->getCateName($taobaoProduct['cid']) : '');
		
			$props = $this->getProductProps($taobaoProduct);
			$props = zh_strtolower(join(',', array_values($props))); // reset the keys
			$productType = zh_strtolower($this->guessProductType($taobaoProduct['title'], $category));
			
			// save the value into table 'adgroups'
			$adgroupMdl = new AdgroupModel();
			$adgroupMdl->where(array('adgroup_id' => $adgroup['adgroup_id']))->save(array(
					'product_type' => $productType ? $productType : 'NULL', // 没有分析出产品类别的, 用NULL标记一下, 以免下次重新分析
					'product_props' => $props
			));
			
			$adgroup['product_type'] = $productType ? $productType : 'NULL';
			$adgroup['product_props'] = $props;
			
		}
	}

	
	/**
	 * 按照 ctr/price 比率来比较关键词的大小.
	 * <strong>注意: </strong>排序之前需要保证avg_price==0元素都已经排除了.
	 *
	 * @param array(k=>v) $a
	 * @param array(k=>v) $b
	 * @return 1: $a<$b; 0: $a==$b; -1:$a>$b;
	 */
	public static function uc_cmp_ctr_price_desc($a, $b) {
		$aRatio = $a['ctr'] / $a['avg_price'];
		$bRatio = $b['ctr'] / $b['avg_price'];
		if($aRatio == $bRatio)
			return 0;
		else
			return $aRatio < $bRatio ? 1 : -1;
	}
	/**
	 * 引流模式, pv倒序排序的比较函数.
	 *
	 * @param array(k=>v) $a
	 * @param array(k=>v) $b
	 * @return number
	 */
	public static function uc_cmp_pv_desc($a, $b) {
		if($a['pv'] == $b['pv']) {
			return 0;
		} else {
			return $a['pv'] < $b['pv'] ? 1: -1;
		}
	}
	/**
	 * 小额投资模式, avg_price升序排列的比较函数.
	 *
	 * @param array(k=>v) $a
	 * @param array(k=>v) $b
	 * @return number
	 */
	public static function uc_cmp_price_asc($a, $b) {
		if($a['avg_price'] == $b['avg_price']) {
			return 0;
		} else {
			return $a['avg_price'] > $b['avg_price'] ? 1: -1;
		}
	}

	/**
	 * 选款推荐[SEO数据]
	 * @return [type] [description]
	 */
	public function getItemSeoData($user_id, $from_to, $sort)
	{
		$res = M('shelpe_seo_rpt_item')
				->field('goods_id,goods_title,goods_img,avg_visitor_value,pay_goods_num,pay_conversion_rate,pv,pay_money_num,avg_time,cart_count,bounce_rate,fav_count')
				->where(array(
						'user_id' => $user_id,
						'from_to' => $from_to 
					))
				->order("{$sort['sort']} {$sort['desc']}")
				->select();

		$listRes = array(
			'total_items' => count($res)
		);
		
		if($listRes['total_items'] > 0)
			$listRes['items'] = $res;

		return $listRes;
	}
}

